Mapping Part 2 — Workbook

In this lesson, we’re going to learn how to analyze and visualize geographic data.

Torn Apart / Separados

Read in Data

The data in this section was drawn from Torn Apart / Separados Project. It maps the locations of Immigration and Customs Enforcement (ICE) detention facilities, as featured in Volume 1.

import pandas as pd
pd.options.display.max_rows = 100
pd.options.display.max_columns = 100

You can find this data on GitHub: https://github.com/xpmethod/torn-apart-open-data/blob/master/iceFacs.csv

fac_df = pd.read_csv("../data/ICE-facilities.csv")
# Drop any blank lat, lon coordinates
fac_df = fac_df.dropna(subset=['lat', 'lon'])
fac_df
lat lon adpSum onWeb Flags fulladdr DETLOC Name Address City County State Zip Circuit AOR Docket Type Type.Detailed ICE.Funded Population.Count Date.of.Last.Use Date.of.First.Use FY18.Max.Population.Count FY18.ADP FY17.ADP FY16.ADP FY15.ADP FY14.ADP FY18.Facility.Bookins FY17.Facility.Bookins FY16.Facility.Bookins FY15.Facility.Bookins FY18.Non.Criminal FY18.Criminal FY17.Non.Criminal FY17.Criminal FY16.Non.Criminal FY16.Criminal FY15.Non.Criminal FY15.Criminal ICE.Threat.Level.1 ICE.Threat.Level.2 ICE.Threat.Level.3 No.ICE.Threat.Level Facility.Operator FY17.Calendar.Days.in.Use FY17...of.Days.in.Use FY17.Total.Mandays FY17.Max.Pop.Count geocodelat geocodelon
0 28.895000 -99.121200 8391 28.8950 NaN 566 VETERANS DRIVE PEARSALL TX 78061 STCDFTX SOUTH TEXAS DETENTION COMPLEX 566 VETERANS DRIVE PEARSALL FRIO TX 78061 5 SNA STC CDF CDF Yes 1735 04.11.2017 27.06.2005 1811 1754 1640 1728 1547 1722 2056 13065 15317 15513 1182 573 1120 520 1115 613 944 603 145 112 311 1187 GEO 372 1.02 598554 1854 28.896498 -99.116863
1 32.036600 -84.771800 8004 32.0366 NaN 146 CCA ROAD LUMPKIN GA 31815 STWRTGA STEWART DETENTION CENTER 146 CCA ROAD LUMPKIN STEWART GA 31815 11 ATL ATL IGSA DIGSA Yes 1917 04.11.2017 01.09.2000 1947 1802 1840 1369 1374 1619 2379 11022 7583 9455 666 1136 861 979 585 785 363 1011 423 344 365 671 CCA 372 1.02 671515 1992 32.037982 -84.772465
2 34.559200 -117.441000 7265 34.5592 NaN 10250 RANCHO ROAD ADELANTO CA 92301 ADLNTCA ADELANTO ICE PROCESSING CENTER 10250 RANCHO ROAD ADELANTO SAN BERNARDINO CA 92301 9 LOS LOS IGSA DIGSA Yes 1747 04.11.2017 29.08.2011 1767 1691 1713 1472 1180 1209 1870 8623 10531 8103 721 970 874 840 626 846 237 942 595 206 164 726 GEO 372 1.02 625414 1918 34.557721 -117.442524
3 32.817700 -111.520000 7096 32.8177 NaN 1705 EAST HANNA RD. ELOY AZ 85131 EAZ ELOY FEDERAL CONTRACT FACILITY 1705 EAST HANNA RD. ELOY PINAL AZ 85131 9 PHO EAZ IGSA DIGSA Yes 1436 04.11.2017 10.03.1991 1444 1401 1378 1433 1401 1483 1310 6873 6254 7079 780 621 759 619 742 691 711 690 230 154 232 785 CCA 372 1.02 502952 1489 32.821231 -111.549772
4 47.249100 -122.421000 6757 47.2491 NaN 1623 E. J STREET TACOMA WA 98421 CSCNWWA NORTHWEST DETENTION CENTER 1623 E. J STREET TACOMA PIERCE WA 98421 9 SEA TAC CDF CDF Yes 1421 04.11.2017 07.06.2001 1438 1391 1423 1411 1132 1400 1556 5961 6019 5207 690 701 655 768 682 729 452 680 358 166 174 693 GEO 372 1.02 519386 1563 47.250214 -122.422746
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
479 42.055299 -104.952540 1 0.0000 NaN 850 MAPLE STREET WHEATLAND WY 82201 PLATTWY PLATTE COUNTY JAIL 850 MAPLE STREET WHEATLAND PLATTE WY 82201 10 DEN DEN USMS IGA USMS IGA Yes 3 04.11.2017 04.04.2000 5 1 0 0 0 0 9 45 53 23 0 1 0 0 0 0 0 0 0 0 0 0 COUNTY (SHERIFF) 61 0.17 90 5 42.055299 -104.952540
480 31.793411 -106.369373 1 0.0000 NaN 8915 MONTANA ST. EL PASO TX 79925 EPCJUVI EL PASO JUVENILE 8915 MONTANA ST. EL PASO EL PASO TX 79925 5 ELP EPC Other HOLD Yes 1 04.11.2017 27.10.1998 17 1 0 0 0 0 565 915 11 17 1 0 0 0 0 0 0 0 0 0 0 1 FEDERAL 59 0.16 61 9 31.793411 -106.369373
481 39.671492 -75.714329 1 0.0000 NaN 970 BROAD STREET NEWARK NJ 7102 NEWHOLD NEW/INS OS HOLD ROOM 970 BROAD STREET NEWARK ESSEX NJ 7102 3 NEW NEW Other HOLD Yes 0 20.10.2017 01.01.1988 2 1 0 0 0 0 4 102 209 264 1 0 0 0 0 0 0 0 0 0 0 1 FEDERAL 37 0.10 50 2 39.671492 -75.714329
482 26.204563 -98.270145 1 0.0000 NaN BENTSEN TOWER, 1701 W BUS HWY 83 MCALLEN TX 78501 USMS3TX US MARSHALS (SOUTH DISTRICT, TEXAS) BENTSEN TOWER, 1701 W BUS HWY 83 MCALLEN HIDALGO TX 78501 5 SNA PIC USMS IGA USMS IGA Yes 0 02.05.2016 26.06.2005 0 0 0 0 1 0 0 2 1 2 0 0 0 0 0 0 0 1 0 0 0 0 FEDERAL 0 0.00 0 0 26.204563 -98.270145
483 41.528728 -73.363545 1 0.0000 NaN BRIDGEWATER STATE HOSPITAL BRIDGEWATER MA 2324 MABSHOS BRIDGEWATER STATE HOSPITAL BRIDGEWATER STATE HOSPITAL BRIDGEWATER PLYMOUTH MA 2324 1 BOS BOS Other HOSPITAL No 0 11.06.2015 09.11.2000 0 0 0 0 1 0 0 1 0 3 0 0 0 0 0 0 0 0 0 0 0 0 HOSPITAL 0 0.00 0 0 41.528728 -73.363545

318 rows × 51 columns

Import Folium

!pip install folium
import folium

Let’s establish a base map

US_map = folium.Map(location=[42, -102], zoom_start=4)
US_map

Add a Circle Marker

There are a few different kinds of markers that we can add to a Folium map, including circles. To make a circle, we can call folium.CircleMarker() with a particular radius and the option to fill in the circle.

You can explore more customization options in the Folium documentation. We’re also going to add a hover tooltip.

# Establish base map
US_map = folium.Map(location=[42, -102], zoom_start=4)

# Loop through rows in the Pandas DataFrame
for index, row in fac_df.iterrows():
    
    # Make a caption
    caption_info = f"""{row['Name']} <br>
                    {row['City']}, {row['State']}""" 
    
    folium.CircleMarker(
                # Latitude, longitude for each marker
                location=[row['lat'], row['lon']],
        
                # Size, fill, color of the circle marker
                radius = 10, fill = True, color='orange',
        
                # Text that goes into the popup or tooltip
                tooltip = caption_info 
                  
                 ).add_to(US_map)

US_map

Your Turn!

  • What is this map showing us about ICE (U.S Immigration and Customs Enforcement) that words alone or even a DataFrame couldn’t capture?

  • What other information might be important to have in the popup or tooltip?

  • What other design choices would you make to improve this map? What consequences could these decisions have in the world?

Pick at least one other category from the DataFrame and include it in the popup or toolip. You can review the existing columns in the DataFrame below.

fac_df.columns
fac_df.sample(5)
lat lon adpSum onWeb Flags fulladdr DETLOC Name Address City County State Zip Circuit AOR Docket Type Type.Detailed ICE.Funded Population.Count Date.of.Last.Use Date.of.First.Use FY18.Max.Population.Count FY18.ADP FY17.ADP FY16.ADP FY15.ADP FY14.ADP FY18.Facility.Bookins FY17.Facility.Bookins FY16.Facility.Bookins FY15.Facility.Bookins FY18.Non.Criminal FY18.Criminal FY17.Non.Criminal FY17.Criminal FY16.Non.Criminal FY16.Criminal FY15.Non.Criminal FY15.Criminal ICE.Threat.Level.1 ICE.Threat.Level.2 ICE.Threat.Level.3 No.ICE.Threat.Level Facility.Operator FY17.Calendar.Days.in.Use FY17...of.Days.in.Use FY17.Total.Mandays FY17.Max.Pop.Count geocodelat geocodelon
472 42.372992 -83.018564 1 0.0000 NaN 5555 CONNER AVENUE, SUITE 3 NORTH DETROIT MI 48213 SAMBCMI SAMARITAN BEHAVIORAL CENTER 5555 CONNER AVENUE, SUITE 3 NORTH DETROIT COUNTY MI 48213 6 DET NaN Other HOSPITAL No 1 04.11.2017 27.03.2013 1 1 0 0 0 0 1 1 0 0 1 0 0 0 0 0 0 0 0 0 0 1 NaN 93 0.25 86 1 42.372992 -83.018564
451 31.803547 -106.288678 1 0.0000 NaN 11541 MONTANA AVE EL PASO TX 79936 IEPHOLD ICE EL PASO HOLD ROOM 11541 MONTANA AVE EL PASO EL PASO TX 79936 5 ELP NaN Other HOLD Yes 0 22.10.2017 05.07.2016 2 1 0 0 0 0 35 419 40 0 1 0 0 0 0 0 0 0 0 0 0 1 NaN 141 0.39 136 2 31.803547 -106.288678
334 40.192202 -75.914124 9 0.0000 NaN 1000 ACADEMY DRIVE MORGANTOWN PA 19543 ABRXSPA ABRAXAS ACADEMY DETENTION CENTER 1000 ACADEMY DRIVE MORGANTOWN BERKS PA 19543 3 PHI BRK IGSA JUVENILE Yes 2 04.11.2017 23.05.2014 2 2 3 4 0 0 2 7 10 3 1 1 2 2 2 2 0 0 1 0 0 1 NaN 372 1.02 1207 5 40.192202 -75.914124
120 43.218700 -70.935100 283 43.2187 NaN 266 COUNTY FARM ROAD DOVER NH 3820 STRAFNH STRAFFORD COUNTY CORRECTIONS 266 COUNTY FARM ROAD DOVER STRAFFORD NH 3820 1 BOS BOS IGSA IGSA Yes 56 04.11.2017 11.02.1991 87 65 95 67 29 27 175 888 471 517 20 45 33 62 24 43 10 18 27 8 9 20 COUNTY (CORRECTIONS) 372 1.02 34578 130 43.218476 -70.940417
361 34.783840 -91.901281 6 0.0000 NaN 203 W. FRONT STREET LONOKE AR 72086 LONPDAR LONOKE POLICE DEPARTMENT 203 W. FRONT STREET LONOKE LONOKE AR 72086 8 NOL FSA IGSA IGSA Yes 1 04.11.2017 12.03.2002 11 2 1 1 1 1 58 292 202 260 0 1 0 1 0 1 0 1 0 1 0 0 CITY 185 0.51 524 15 34.783840 -91.901281

Add another category to the tooltip/popup

# Establish base map
US_map = folium.Map(location=[42, -102], zoom_start=4)

# Loop through rows in the Pandas DataFrame
for index, row in fac_df.iterrows():
    
    # Make a caption -- <br> is HTML for a line break
    caption_info = f"""{row['Name']} <br>
                    {row['City']}, {row['State']} <br>
                    More info here: """ 
                    # ☝️ Add another category into the caption above and/or alter the design! ☝️

    folium.CircleMarker(
                # Latitude, longitude for each marker
                location=[row['lat'], row['lon']],
        
                # Size, fill, color of the circle marker
                radius = 10, fill = True, color='orange',
        
                # Text that goes into the popup or tooltip
                tooltip = caption_info 
                  
                 ).add_to(US_map)

US_map

Save the map as an HTML file

US_map.save("ICE-map.html")

Choropleth Maps

Choropleth map = a map where areas are shaded according to a value

The data in this section was drawn from Torn Apart / Separados Project. This data maps the “cumulative ICE awards since 2014 to contractors by congressional district,” as featured in Volume 2.

To create a chropleth map with Folium, we need to pair a “geo.json” file (which indicates which parts of the map to shade) with a CSV file (which includes the variable that we want to shade by).

The following data was drawn from the Torn Apart / Separados project

First, we will examine the geoJSON file. To learn more about geoJSON, you can explore here: https://geojson.io/#map=13/42.4349/-76.5028

geojson_df = pd.read_json("../data/ICE_money_districts.geo.json")
geojson_df['features'][0]
{'type': 'Feature',
 'geometry': {'type': 'MultiPolygon',
  'coordinates': [[[[-87.166581, 31.519561],
     [-87.135051, 31.642417000000002],
     [-87.0511, 31.71834],
     [-86.905899, 31.753035],
     [-86.906899, 31.830628],
     [-86.908939, 31.961673],
     [-86.857583, 31.962167],
     [-86.448198, 31.964629],
     [-86.405005, 31.963775],
     [-86.406276, 32.050731],
     [-86.408272, 32.208976],
     [-86.300769, 32.219544],
     [-86.307426, 32.307219],
     [-86.229056, 32.315688],
     [-86.299216, 32.326703],
     [-86.322056, 32.378075],
     [-86.436067, 32.331314],
     [-86.484678, 32.284473],
     [-86.474479, 32.331547],
     [-86.496774, 32.344437],
     [-86.653419, 32.397247],
     [-86.781354, 32.392494],
     [-86.816107, 32.30997],
     [-86.814912, 32.340803],
     [-86.906403, 32.536712],
     [-86.917595, 32.664169],
     [-86.71339, 32.661732],
     [-86.714219, 32.705694],
     [-86.413116, 32.707386],
     [-86.413335, 32.750591],
     [-86.374974, 32.75358],
     [-86.336694, 32.76813],
     [-86.31948, 32.753698],
     [-86.007187, 32.754984],
     [-85.87986, 32.754528],
     [-85.886148, 32.493053],
     [-85.852625, 32.475747],
     [-86.023012, 32.419978],
     [-86.046402, 32.406146],
     [-86.192284, 32.43613],
     [-86.19919, 32.382142],
     [-86.13857, 32.367823],
     [-86.190647, 32.291901],
     [-86.118065, 32.223873],
     [-86.112586, 32.15589],
     [-85.997859, 32.141605],
     [-85.999157, 32.250543],
     [-85.986557, 32.272342],
     [-85.919293, 32.274382],
     [-85.898689, 32.274987],
     [-85.898539, 32.305289],
     [-85.856218, 32.231975],
     [-85.433543, 32.234648],
     [-85.427441, 32.146551],
     [-85.410241, 32.146651],
     [-85.257834, 32.147931],
     [-85.185067, 32.061708],
     [-85.0514111279128, 32.062256083622],
     [-85.06359054298468, 31.991857],
     [-85.067829, 31.967358],
     [-85.114031, 31.89336],
     [-85.141831, 31.839261],
     [-85.1291593155832, 31.7802782680766],
     [-85.1254405773425, 31.7629687034183],
     [-85.11893, 31.732664],
     [-85.12553, 31.694965],
     [-85.058169, 31.620227],
     [-85.05796, 31.57084],
     [-85.041881, 31.544684],
     [-85.0516814295062, 31.519540329141698],
     [-85.071621, 31.468384],
     [-85.066005, 31.431363],
     [-85.092487, 31.362881],
     [-85.087929, 31.321648],
     [-85.08882996353049, 31.308647755496597],
     [-85.089774, 31.295026],
     [-85.108192, 31.258591],
     [-85.107516, 31.186451],
     [-85.035615, 31.108192],
     [-85.0211075623628, 31.0754638723064],
     [-85.011392, 31.053546],
     [-85.002499, 31.000682],
     [-85.031285, 31.000647],
     [-85.145959, 31.000693],
     [-85.333319, 30.999555],
     [-85.4882982889391, 30.9979646232873],
     [-85.4980015850895, 30.997865049361398],
     [-85.57949757633828, 30.9970287484109],
     [-85.749715, 30.995282],
     [-85.893632, 30.993455],
     [-86.0350381049506, 30.9937496073023],
     [-86.1872480489813, 30.9940667234622],
     [-86.364974, 30.994437],
     [-86.3886446620004, 30.9945282152752],
     [-86.563494, 30.995202],
     [-86.68824096424609, 30.996201890001498],
     [-86.700251, 31.008901],
     [-86.700282, 31.192217],
     [-86.772519, 31.202243],
     [-86.763961, 31.261293],
     [-87.427455, 31.260386],
     [-87.166581, 31.519561]]]]},
 'properties': {'state': 'Alabama',
  'districtNumber': 2,
  'districtName': 'ta-ordinal-nd-m',
  'party': 'republican',
  'profiteer': {'name': 'MMI Outdoor', 'value': 38576},
  'dom_id': 'district-alabama-2',
  'representative': 'Martha Roby',
  'representative_photo_url': 'https://upload.wikimedia.org/wikipedia/commons/5/55/Martha_roby_113_congressional_portrait_%28cropped%29.jpg',
  'total_value': 38577.4,
  'district_url': 'https://en.wikipedia.org/wiki/Alabama%27s_2nd_congressional_district'}}
districts_df = pd.read_csv("../data/ICE_money_districts.csv")
districts_df = districts_df.dropna(subset=['districtName', 'representative'])
districts_df
id id2 state districtNumber districtName party district_url representative representative_photo_url total_awards
0 5001500US0101 101 Alabama 1 ta-ordinal-st-m republican https://en.wikipedia.org/wiki/Alabama%27s_1st_congressional_district Bradley Byrne https://upload.wikimedia.org/wikipedia/commons/7/71/Rep_Bradley_Byrne_%28cropped%29.jpg 0.00
1 5001500US0102 102 Alabama 2 ta-ordinal-nd-m republican https://en.wikipedia.org/wiki/Alabama%27s_2nd_congressional_district Martha Roby https://upload.wikimedia.org/wikipedia/commons/5/55/Martha_roby_113_congressional_portrait_%28cropped%29.jpg 38577.40
2 5001500US0103 103 Alabama 3 ta-ordinal-rd-m republican https://en.wikipedia.org/wiki/Alabama%27s_3rd_congressional_district Mike Rogers https://upload.wikimedia.org/wikipedia/commons/e/ee/Mike_Rogers_official_photo_%28cropped%29.jpg 0.00
3 5001500US0104 104 Alabama 4 ta-ordinal-th-m republican https://en.wikipedia.org/wiki/Alabama%27s_4th_congressional_district Robert Aderholt https://upload.wikimedia.org/wikipedia/commons/9/9f/Rep._Robert_B._Aderholt_%28cropped%29.jpg 171873.55
4 5001500US0105 105 Alabama 5 ta-ordinal-th-m republican https://en.wikipedia.org/wiki/Alabama%27s_5th_congressional_district Mo Brooks https://upload.wikimedia.org/wikipedia/commons/b/b6/Mo_Brooks_Portrait_%28cropped%29.jpg 40346.00
... ... ... ... ... ... ... ... ... ... ...
432 5001500US5506 5506 Wisconsin 6 ta-ordinal-th-m republican https://en.wikipedia.org/wiki/Wisconsin%27s_6th_congressional_district Glenn Grothman https://upload.wikimedia.org/wikipedia/commons/1/16/Glenn_Grothman_official_congressional_photo_%28cropped%29.jpg 3242401.61
433 5001500US5507 5507 Wisconsin 7 ta-ordinal-th-m republican https://en.wikipedia.org/wiki/Wisconsin%27s_7th_congressional_district Sean Duffy https://upload.wikimedia.org/wikipedia/commons/d/d7/Sean_Duffy_official_congressional_photo_%28cropped%29.jpg 32698.55
434 5001500US5508 5508 Wisconsin 8 ta-ordinal-th-m republican https://en.wikipedia.org/wiki/Wisconsin%27s_8th_congressional_district Mike Gallagher https://upload.wikimedia.org/wikipedia/commons/a/ad/Mike_Gallagher_Official_Portrait_2017_%28cropped%29.png 237392.73
435 5001500US5600 5600 Wyoming 0 ta-at-large-district republican https://en.wikipedia.org/wiki/Wyoming%27s_at-large_congressional_district Liz Cheney https://upload.wikimedia.org/wikipedia/commons/d/dd/Liz_Cheney_official_portrait.jpg 0.00
436 5001500US7298 7298 Puerto Rico 98 ta-ordinal-th-m no-rep https://en.wikipedia.org/wiki/Puerto_Rico%27s_at-large_congressional_district Resident Commissioner Jenniffer González https://upload.wikimedia.org/wikipedia/commons/thumb/7/7a/Official_portrait_of_Resident_Commissioner_Jenniffer_Gonzalez.jpg/800px-Official_portrait_of_Resident_Commissioner_Jenniffer_Gonzalez.jpg 323102.10

431 rows × 10 columns

Your Turn!

Run the following cells and inspect the maps. Then answer the discussion questions at the bottom.

Here we are creating a choropleth map by pairing geoJSON data and a Pandas DataFrame.

US_map = folium.Map(location=[42, -102], zoom_start=4)

choro_map = folium.Choropleth(
    # Geojson data
    geo_data = "../data/ICE_money_districts.geo.json",
    # DataFrame data
    data = districts_df,
    # Columns to include from DataFrame
    columns = ['districtName', 'total_awards'],
    # Shared category from geojson
    key_on = 'feature.properties.districtName',
    # Color palette
    fill_color = 'GnBu',
    line_opacity = 0.2,
    legend_name= 'Total ICE Money Received'
).add_to(US_map)

US_map

Add a Tooltip to Choropleth

tooltip = folium.features.GeoJson(
    "../data/ICE_money_districts.geo.json",
    tooltip=folium.features.GeoJsonTooltip(
        fields=['representative',
                'state',
                'party', 
                'total_value', 
                'profiteer'],
                localize=True))
US_map.add_child(tooltip)
US_map

Discussion

To see how our map compares to the Torn Apart / Separados map, you can explore here: http://xpmethod.columbia.edu/torn-apart/volume/2/

  • What does this map (or the Torn Apart/Separados map) show us about ICE that is different from the previous map?

  • How do the design and tooltip choices of this map influence the argument being made?